package com.google.code.mircle;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Enumeration;
import java.util.HashMap;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;

import com.google.code.mircle.core.Core;
import com.google.code.mircle.core.Model;
import com.google.code.mircle.core.Plugin;
import com.google.code.mircle.core.Root;
import com.google.code.mircle.core.View;
import com.google.code.mircle.core.transfers.DirectView;
import com.google.code.mircle.core.transfers.Transfer;
import com.google.code.mircle.os.mac.Apple;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.io.StreamException;

public class App {

    HashMap<Model, Root> modelPluginMap = new HashMap<Model, Root>();
    HashMap<Model, View> modelViewRuntimeMap = new HashMap<Model, View>();
    JTree left;
    JPanel right;
    JFrame main;
    JSplitPane split;

    int dividerSize;
    int dividerLocation;

    Core core = new Core(this);

    State state = new State();

    @XStreamAlias("AppState")
    static class State {
        String model;
    }

    static class MVCRenderer {

        Object user;
        String name;

        public MVCRenderer(Object r, String name) {
            this.user = r;
            this.name = name;
        }

        public String toString() {
            return name;
        }
    }

    static class EditableListPanel extends JPanel {
        private static final long serialVersionUID = 785927898208696335L;

        EditableListPanel(Throwable e) {
            super(new BorderLayout());

            JTextArea text = new JTextArea();
            text.setEditable(false);
            text.setText(ExceptionUtils.getStackTrace(e));
            text.setBackground(getBackground());
            add(text, BorderLayout.CENTER);
        }
    }

    public App() {
        registerUnhalted();

        createWindow();
        loadPlugins();

        core.load();

        if (Apple.isAvailable())
            Apple.register(this);
    }

    static void report(Thread t, Throwable e) {
        e.printStackTrace();
        JOptionPane.showMessageDialog(null, new EditableListPanel(e), "Fatal eror", JOptionPane.ERROR_MESSAGE);
    }

    void registerUnhalted() {
        UncaughtExceptionHandler u = new UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                report(t, e);
                System.exit(1);
            }
        };

        Thread.setDefaultUncaughtExceptionHandler(u);
    }

    public void mircle(String uri) {
        String u = uri.toString();

        Transfer t = core.transfers.getDirect().download(u);

        if (!t.isAlive())
            core.transfers.getDirect().start(t);

        modelSwitch(core.transfers.getDirect().getClass());

        DirectView view = (DirectView) getView(core.transfers.getDirect());
        view.select(t);
    }

    public void modelSwitch(Class<? extends Model> m) {
        DefaultTreeModel model = (DefaultTreeModel) left.getModel();
        DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
        modelSwitch(root, m);
    }

    boolean modelSwitch(DefaultMutableTreeNode root, Class<? extends Model> m) {
        for (Enumeration<?> e = root.children(); e.hasMoreElements();) {
            DefaultMutableTreeNode n = (DefaultMutableTreeNode) e.nextElement();
            MVCRenderer r = (MVCRenderer) n.getUserObject();
            if (m.isAssignableFrom(r.user.getClass())) {
                left.setSelectionPath(new TreePath(n.getPath()));
                return true;
            }

            if (!n.isLeaf()) {
                if (modelSwitch(n, m))
                    return true;
            }
        }

        return false;
    }

    public void close() {
        core.close();
    }

    public void loadPlugins() {
        DefaultTreeModel model = (DefaultTreeModel) left.getModel();
        DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
        for (Plugin p : core.plugins) {
            if (p instanceof Root) {
                Root r = (Root) p;
                DefaultMutableTreeNode d = new DefaultMutableTreeNode(new MVCRenderer(r, p.getName()));
                Model[] models = r.getModels();
                if (models != null) {
                    for (Model m : models) {
                        d.add(new DefaultMutableTreeNode(new MVCRenderer(m, m.toString())));
                        modelPluginMap.put(m, r);
                    }
                }
                root.add(d);
            }
            if (p instanceof Model)
                modelPluginMap.put((Model) p, (Root) p);
        }
        model.nodeStructureChanged((MutableTreeNode) model.getRoot());

        for (int i = 0; i < left.getRowCount(); i++)
            left.expandRow(i);
    }

    public View getView(Model o) {
        return modelViewRuntimeMap.get(o);
    }

    JComponent switchView(Model model) {
        View view = getView(model);
        if (view == null) {
            Root p = modelPluginMap.get(model);

            view = p.createView(model);

            modelViewRuntimeMap.put(model, view);
        }

        return (JComponent) view;
    }

    public void createWindow() {
        main = new JFrame("Share - BETA");

        right = new JPanel(new BorderLayout());

        left = new JTree(new DefaultTreeModel(new DefaultMutableTreeNode()));
        left.setRootVisible(false);
        left.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        left.addTreeSelectionListener(new TreeSelectionListener() {
            public void valueChanged(TreeSelectionEvent e) {
                TreePath p = e.getPath();
                DefaultMutableTreeNode o = (DefaultMutableTreeNode) p.getLastPathComponent();
                MVCRenderer oo = (MVCRenderer) o.getUserObject();

                right.removeAll();
                JComponent view;
                if (Model.class.isAssignableFrom(oo.user.getClass())) {
                    view = switchView((Model) oo.user);
                } else {
                    view = new JPanel();
                }
                right.add(view, BorderLayout.CENTER);
                view.revalidate();
                view.repaint();
            }
        });
        left.addMouseListener(new MouseListener() {
            @Override
            public void mouseReleased(MouseEvent e) {
                JTree tree = (JTree) e.getSource();
                if (e.getButton() == MouseEvent.BUTTON3) {
                    int selRow = tree.getRowForLocation(e.getX(), e.getY());
                    TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
                    if (selRow != -1) {
                        tree.setSelectionPath(selPath);

                        DefaultMutableTreeNode t = (DefaultMutableTreeNode) selPath.getLastPathComponent();
                        MVCRenderer o = (MVCRenderer) t.getUserObject();
                        if (o.user instanceof Model) {
                            Model m = (Model) o.user;
                            Root p = modelPluginMap.get(m);
                            JPopupMenu pop = p.contextMenu(m);
                            if (pop != null)
                                pop.show(tree, e.getX(), e.getY());
                        }
                        if (o.user instanceof Root) {
                            Root p = (Root) o.user;
                            JPopupMenu pop = p.contextMenu(null);
                            if (pop != null)
                                pop.show(tree, e.getX(), e.getY());
                        }
                    }
                }
            }

            @Override
            public void mousePressed(MouseEvent arg0) {
            }

            @Override
            public void mouseExited(MouseEvent arg0) {
            }

            @Override
            public void mouseEntered(MouseEvent arg0) {
            }

            @Override
            public void mouseClicked(MouseEvent arg0) {
            }
        });

        split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, right);
        split.setOneTouchExpandable(true);
        split.setDividerLocation(250);

        main.addWindowListener(new WindowListener() {

            @Override
            public void windowOpened(WindowEvent arg0) {
            }

            @Override
            public void windowIconified(WindowEvent arg0) {
            }

            @Override
            public void windowDeiconified(WindowEvent arg0) {
            }

            @Override
            public void windowDeactivated(WindowEvent arg0) {
            }

            @Override
            public void windowClosing(WindowEvent arg0) {
            }

            @Override
            public void windowClosed(WindowEvent arg0) {
                hide();
            }

            @Override
            public void windowActivated(WindowEvent arg0) {
            }
        });

        main.getContentPane().add(split);
        main.pack();
        main.setSize(new Dimension(1300, 800));
        main.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
        main.setLocationRelativeTo(null);

        Icon model = new ImageIcon(App.class.getResource("/model.png"));
        Icon plugin = new ImageIcon(App.class.getResource("/plugin.png"));
        DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) left.getCellRenderer();
        renderer.setLeafIcon(plugin);
        renderer.setClosedIcon(plugin);
        renderer.setOpenIcon(plugin);
        renderer.setLeafIcon(model);
    }

    public void show() {
        if (!main.isVisible())
            main.setVisible(true);
    }

    public void hide() {
        main.setVisible(false);
    }

    XStream xstream() {
        XStream stream = new XStream();
        stream.processAnnotations(new Class[] { State.class });
        return stream;
    }

    public void save(File f) {
        DefaultMutableTreeNode n = (DefaultMutableTreeNode) left.getSelectionPath().getLastPathComponent();

        MVCRenderer m = (MVCRenderer) n.getUserObject();

        state.model = m.user.getClass().getName();

        XStream stream = xstream();
        String xml = stream.toXML(state);
        core.save(xml, FileUtils.getFile(f, "app.xml"));
    }

    @SuppressWarnings("unchecked")
    public void load(File f) {
        XStream stream = xstream();

        try {
            state = (State) stream.fromXML(FileUtils.getFile(f, "app.xml"));
        } catch (StreamException e) {
            return;
        }

        try {
            modelSwitch((Class<? extends Model>) Class.forName(state.model));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void toggleFullScreen() {
        if (left.isVisible()) {
            dividerSize = split.getDividerSize();
            dividerLocation = split.getDividerLocation();
            split.setDividerSize(0);
            left.setVisible(false);
        } else {
            offFullScreen();
        }
    }

    public void offFullScreen() {
        if (!left.isVisible()) {
            left.setVisible(true);
            split.setDividerSize(dividerSize);
            split.setDividerLocation(dividerLocation);
        }
    }

    public static void main(String[] arg) {
        try {
            App app = new App();
            app.show();

            for (String s : arg) {
                app.mircle(s);
            }
        } catch (Exception e) {
            report(Thread.currentThread(), e);
            System.exit(1);
        }
    }
}
